﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CommonLib {
    public class Tracer {
        private Screen screen;
        private Scene scene;

        public Tracer(Screen scr, Scene sce) {
            this.screen = scr;
            this.scene = sce;

        }
        public Screen Screen {
            get { return this.screen; }
        }
        public Primitive findNearest(Ray ray, ref double distance, ref double orientation) {
            Primitive nearest = null;

            for (int j = 0; j < scene.CountPrimitive(); j++) {
                Primitive tmp = scene.GetPrimitive(j);
                double currOri;

                currOri = tmp.isIntersect(ray, ref distance);
                if (currOri == Orientation.Nonexistent)
                    continue;
                nearest = tmp;
                orientation = currOri;
            }
            return nearest;
        }
        public Vector3c estimateDiffuse(Ray ray, Primitive nearest, Vector3c node) {
            //Vector3 coeff = new Vector3();
            Vector3c result = new Vector3c();

            for (int j = 0; j < scene.CountLight(); j++) {
                Primitive light = scene.GetLight(j);

                //Vector3 primNormal = nearest.getNormal(node);
                //Vector3 reflRay = ray.direction - 2.0 * ((primNormal & ray.direction) * primNormal);
                //Vector3 dir2light = - light.getNormal(node);
                //Ray ray2light = new Ray(node + (DoubleTester.Epsilon * dir2light), dir2light);
                Vector3c primNormal = nearest.getNormal(node);
                Vector3c reflRay = new Vector3c(ray.direction).mac(-2.0 * primNormal.dot(ray.direction), primNormal);
                Vector3c dir2light = light.getNormal(node).mul(-1.0);
                Vector3c position = new Vector3c(node).mac(DoubleTester.Epsilon, dir2light);
                Ray ray2light = new Ray(position, dir2light);
                double dist2light = Double.MaxValue;
                double shadow = 1.0;
                double orientation = Orientation.Nonexistent;

                // shadow
                if (light.isIntersect(ray2light, ref dist2light) == Orientation.Exterior) {
                    dist2light -= DoubleTester.Epsilon;
                    Primitive closer = findNearest(ray2light, ref dist2light, ref orientation);
                    if (closer != null)
                        shadow = 0.0;
                }

                // diffuse
                double arg = primNormal.dot(dir2light);
                if (0 < arg) {
                    result.mac(nearest.Material.diffuse * arg * shadow, light.Material.color);
                }

                // specular
                arg = dir2light.dot(reflRay);
                if (0 < arg) {
                    result.mac(nearest.Material.specular * Math.Pow(arg, 20.0) * shadow, light.Material.color);
                }
            }
            return result.mul(nearest.Material.color);
        }
        public Vector3c reflect(Primitive nearest, Vector3c node, Ray ray, int depth) {
            //Vector3 primNormal = nearest.getNormal(node);
            //Vector3 reflDirection = ray.direction - 2.0 * ((ray.direction & primNormal) * primNormal);
            //Ray reflRay = new Ray(node + (DoubleTester.Epsilon * reflDirection), reflDirection);
            //Vector3 reflColor = new Vector3();
            //Vector3 reflNode = new Vector3();
            //raytrace(reflRay, ref reflColor, ref reflNode, depth - 1);

            //return nearest.Material.color * nearest.Material.reflection * reflColor;
            Vector3c primNormal = nearest.getNormal(node);
            Vector3c reflDirection = new Vector3c(ray.direction).mac(-2.0 * ray.direction.dot(primNormal), primNormal);
            Vector3c reflPosition = new Vector3c(node).mac(DoubleTester.Epsilon, reflDirection);

            Ray reflRay = new Ray(reflPosition, reflDirection);
            Vector3c reflColor = new Vector3c();
            Vector3c reflNode = new Vector3c();
            raytrace(reflRay, ref reflColor, ref reflNode, depth - 1);
            return reflColor.mul(nearest.Material.reflection).mul(nearest.Material.color);
        }
        public Vector3c refract(Primitive nearest, Vector3c node, Ray ray, int depth, double orientation) {
            //Vector3 primNormal = (- orientation) * nearest.getNormal(node);
            //double nab = (orientation == Orientation.Exterior ? 
            //    nearest.Material.refrIndex : 1.0 / nearest.Material.refrIndex);
            //double cosA = ray.direction & primNormal;
            Vector3c primNormal = nearest.getNormal(node).mul(-orientation);
            double nab = (orientation == Orientation.Exterior ?
                nearest.Material.refrIndex : 1.0 / nearest.Material.refrIndex);
            double cosA = ray.direction.dot(primNormal);

            // refraction
            //double sqrt = Math.Sqrt(cosA * cosA + nab * nab - 1);
            //Vector3 refrDirection = (ray.direction + sqrt - ray.direction * cosA) / nab * primNormal;
            //Ray refrRay = new Ray(node + refrDirection * DoubleTester.Epsilon, refrDirection);
            //Vector3 refrColor = new Vector3();
            //Vector3 refrNode = new Vector3();
            double sqrt = Math.Sqrt(cosA * cosA + nab * nab - 1);
            Vector3c refrDirection = new Vector3c(ray.direction).mac(sqrt - cosA, primNormal).div(nab);
            Vector3c refrPosition = new Vector3c(node).mac(DoubleTester.Epsilon, refrDirection);

            Ray refrRay = new Ray(refrPosition, refrDirection);
            Vector3c refrColor = new Vector3c();
            Vector3c refrNode = new Vector3c();
            if (nearest.Material.refraction <= 0 || cosA <= 0 || (nearest is Sphere) == false)
                return new Vector3c();
            raytrace(refrRay, ref refrColor, ref refrNode, depth - 1);
            return refrColor.mul(nearest.Material.refraction).mul(nearest.Material.color);
        }
        public Primitive raytrace(Ray ray, ref Vector3c color, ref Vector3c node, int depth) {
            Primitive nearest = null;
            double distance = Double.MaxValue;
            double orientation = Orientation.Nonexistent;

            if (depth <= 0)
                return null;

            nearest = findNearest(ray, ref distance, ref orientation);
            if (nearest == null)
                return nearest;
            if (nearest.Light) {
                //color = nearest.Material.color;
                //color = new Vector3c(1.0, 1.0, 1.0);
                color.set(1.0, 1.0, 1.0);
                return nearest;
            }

            //node = (distance * ray.direction) + ray.position;
            //color = estimateDiffuse(ray, nearest, node);
            //color += reflect(nearest, node, ray, depth);
            //color += refract(nearest, node, ray, depth, orientation);
            node.mac(distance, ray.direction).add(ray.position);
            color = estimateDiffuse(ray, nearest, node);
            color.add(reflect(nearest, node, ray, depth));
            color.add(refract(nearest, node, ray, depth, orientation));
            return nearest;
        }
        public void render() {
            Primitive prim;

            for (int i = 0; i < screen.getSize(); i++) {
                Vector3c color = new Vector3c();
                Vector3c node = new Vector3c();

                Ray ray = screen.getRay(i);
                prim = raytrace(ray, ref color, ref node, 3);
                screen.setPixel(i, color);
            }
        }
    }
}
